using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Data.Schema.Extensibility;
using Microsoft.Data.Schema.SchemaModel;
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.Sql.SchemaModel;
using Microsoft.Data.Schema.Sql;
using Microsoft.Data.Schema.StaticCodeAnalysis;
using System.Text.RegularExpressions;

using System.Linq;

namespace CallCreditStandards
{
    /// <summary>
    /// This is a SQL rule which returns a warning message 
    /// whenever there is a ........................................
    /// This rule only applies to .................................
    /// </summary>

    [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
    [DataRuleAttribute(
        CallCreditStandardsConstants.NameSpace,
        CallCreditStandardsConstants.CheckClusteredIndexesAreNotDisabledInCodeRuleId,
        CallCreditStandardsConstants.ResourceBaseName,
        CallCreditStandardsConstants.CheckClusteredIndexesAreNotDisabledInCode_RuleName,
        CallCreditStandardsConstants.CategoryDatabaseStructures,
        DescriptionResourceId = CallCreditStandardsConstants.CheckClusteredIndexesAreNotDisabledInCode_ProblemDescription)]
    [SupportedElementType(typeof(ISqlProcedure))]
    [SupportedElementType(typeof(ISqlTrigger))]
    [SupportedElementType(typeof(ISqlFunction))]
    [SupportedElementType(typeof(ISqlView))]
    [SupportedElementType(typeof(ISqlTable))]
    public class CheckClusteredIndexesAreNotDisabledInCodeRule : StaticCodeAnalysisRule
    {
        #region Overrides
        /// <summary>
        /// Analyze the model element
        /// </summary>
        public override IList<DataRuleProblem> Analyze(DataRuleSetting ruleSetting, DataRuleExecutionContext context)
        {
            // (Re)-Load Environment settings
            CallCreditSettings.LoadSettings();
            // Get Model collation 
            SqlComparer.Comparer = context.SchemaModel.Comparer;
        
            List<DataRuleProblem> problems = new List<DataRuleProblem>();

            IModelElement modelElement = context.ModelElement;

            // casting to SQL specific 
            SqlSchemaModel sqlSchemaModel = modelElement.Model as SqlSchemaModel;
            Debug.Assert(sqlSchemaModel != null, "SqlSchemaModel is expected");

            ISqlModelElement sqlElement = modelElement as ISqlModelElement;
            Debug.Assert(sqlElement != null, "ISqlModelElement is expected");

            // Get ScriptDom for this model element
            TSqlFragment sqlFragment = context.ScriptFragment as TSqlFragment;
            Debug.Assert(sqlFragment != null, "TSqlFragment is expected");

            // Get Database Schema and name of this model element.
            string owningObjectSchema = sqlElement.Name.Parts[0];

            // Refresh cached index/constraints lists from Model
            CallCreditSettings.RefreshConstraintsAndIndexesCache(sqlSchemaModel);

            // visitor to get the occurrences of statements 
            CheckClusteredIndexesAreNotDisabledInCodeVisitor checkClusteredIndexesAreNotDisabledInCodeVisitor = new CheckClusteredIndexesAreNotDisabledInCodeVisitor();
            sqlFragment.Accept(checkClusteredIndexesAreNotDisabledInCodeVisitor);
            List<AlterIndexStatement> alterIndexStatements = checkClusteredIndexesAreNotDisabledInCodeVisitor.Objects;
            List<AlterIndexStatement> issues = new List<AlterIndexStatement>();

            if (alterIndexStatements.Count > 0)
            {

                //IList<ISqlIndex> ixs = CallCreditSettings.getIndexes;
                //IList<ISqlPrimaryKeyConstraint> pcons = CallCreditSettings.getPrimaryKeys;

                List<ISqlIndex> clusteredindexes = CallCreditSettings.getIndexes
                                                        .Where(n => (n.Name.ExternalParts == null
                                                                     || n.Name.ExternalParts.Count == 0
                                                                     )
                                                                  && n.IsClustered
                                                              ).Select(n => n).ToList();
                List<ISqlPrimaryKeyConstraint> clusteredprimaryconstraints = CallCreditSettings.getPrimaryKeys
                                                        // can't disable an anonymous constraint
                                                        .Where(n => (n.Name != null
                                                                     && ( n.Name.ExternalParts == null
                                                                        || n.Name.ExternalParts.Count == 0
                                                                        )
                                                                     )
                                                                     && n.IsClustered
                                                              ).Select(n => n).ToList();

                foreach (var alter in alterIndexStatements)
                {
                    bool done = false;
                    foreach (var index in clusteredindexes.ToList())
                    {
                        string idx_schema = index.IndexedObject.Name.Parts[0];
                        string alter_schema = alter.OnName.SchemaIdentifier.Value;
                        // best guess efforts
                        if (string.IsNullOrEmpty(alter_schema))
                        {
                            alter_schema = owningObjectSchema;
                        }
                        if (SqlComparer.CompareEqual(alter_schema, idx_schema)
                            //index.Name.Parts[0] = schema
                            //index.Name.Parts[1] = tablename
                            //index.Name.Parts[2] = indexname
                            && SqlComparer.CompareEqual(alter.Name.Value, index.Name.Parts[2])
                            && SqlComparer.CompareEqual(alter.OnName.BaseIdentifier.Value, index.IndexedObject.Name.Parts[1])
                            )
                        {
                            done = true;
                            issues.Add(alter);
                            break;
                        }
                    }
                    if (!done)
                    {
                        foreach (var cons in clusteredprimaryconstraints.ToList())
                        {
                            string cons_schema = cons.Name.Parts[0];
                            string alter_schema = alter.OnName.SchemaIdentifier.Value;
                            // best guess efforts
                            if (string.IsNullOrEmpty(alter_schema))
                            {
                                alter_schema = owningObjectSchema;
                            }
                            if (SqlComparer.CompareEqual(alter_schema, cons_schema)
                                //cons.Name.Parts[0] = schema
                                //cons.Name.Parts[1] = constraintname
                                && SqlComparer.CompareEqual(alter.Name.Value, cons.Name.Parts[1])
                                //cons.DefiningTable.Name.Parts[0] = schema
                                //cons.DefiningTable.Name.Parts[1] = tablename
                                && SqlComparer.CompareEqual(alter.OnName.BaseIdentifier.Value, cons.DefiningTable.Name.Parts[1])
                                )
                            {
                                done = true;
                                issues.Add(alter);
                                break;
                            }
                        }
                    }
                }
            }

            // Create problems for each object
            foreach (TSqlFragment issue in issues)
            {
                DataRuleProblem problem = new DataRuleProblem(this,
                                            String.Format(CultureInfo.CurrentCulture, this.RuleProperties.Description, SqlRuleUtils.GetElementName(sqlSchemaModel, sqlElement)),
                                            sqlElement);

                SqlRuleUtils.UpdateProblemPosition(problem, issue.StartOffset, issue.FragmentLength);
                problems.Add(problem);
            }


            return problems;
        }

        #endregion    
 
    }
}
